#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include "btoken.h"

//tokenizer part
static const char* _keywordtable[] = {
	"var", "if", "elif", "else", "for", "while",
	"function", "condition", "action", "trigger", "end"
};

int substricmp(const char* begin, const char* end, const char* matchstring) {
	// compare [begin, end) with matchstring.
	while(begin < end) {
		char ch1 = tolower(*begin), ch2 = tolower(*matchstring);
		if(ch1 != ch2) return ch1 - ch2;
		begin++; matchstring++;
	}
	if(*matchstring != 0) return -tolower(*matchstring);
	return 0;
}


//matcher for indivisual tokens
const char* GetName      (const char* str, Token* token);
const char* GetNumber    (const char* str, Token* token);
const char* GetString    (const char* str, Token* token);
const char* GetOperator  (const char* str, Token* token);
const char* GetLParen    (const char* str, Token* token);
const char* GetRParen    (const char* str, Token* token);
const char* GetSeperator (const char* str, Token* token);

const char* (*TokenParser[7])(const char*, Token*) = {
	GetName,
	GetNumber,
	GetString,
	GetOperator,
	GetLParen,
	GetRParen,
	GetSeperator
};



// parses keyword and variable/function/etc NAME whatever.
const char* GetName(const char* str, Token* token) {
	//name validation is as same as C. _varname thing.
	auto isVaildChar = [](char ch) { return ch == '_' || isalnum(ch); }; //alphabet, nummeric, and underscore

	if(!isVaildChar(*str)) return NULL; //not vaild
	else if(isdigit(*str)) return NULL; //starting with 0 -> nummeric maybe

	//read as possible
	token->m_begin = str;
	while(isVaildChar(*(++str)));
	token->m_end = str;
	
	if(
		substricmp(token->m_begin, token->m_end, "and") == 0 ||
		substricmp(token->m_begin, token->m_end, "or")  == 0 ||
		substricmp(token->m_begin, token->m_end, "xor") == 0
	) return NULL; //these are operators

	token->m_tokentype = TOKEN_VARNAME;

	//detect keyword
	int keywordn = sizeof(_keywordtable) / sizeof(const char*);
	int i;
	for(i = 0 ; i < keywordn ; i++) {
		if(substricmp(token->m_begin, token->m_end, _keywordtable[i]) == 0) break;
	}

	//if the token is a keyword, then parse it as enummeration
	if(i != keywordn) {
		token->m_tokentype = TOKEN_KEYWORD;
		token->m_parsed = malloc(sizeof(int));
		*(int*)token->m_parsed = i;
	}
	else {
		token->m_tokentype = TOKEN_VARNAME;
		token->m_parsed = malloc(token->m_end - token->m_begin);
		memcpy(token->m_parsed, token->m_begin, token->m_end - token->m_begin);
	}

	return str;
}


// number parsing. uses strtol
const char* GetNumber(const char* str, Token* token) {
	char* eptr;
	long val;

	//parse str.
	if(!isdigit(str[0])) return NULL;

	if(str[0] == '0') {
		if(str[1] == 'x' || str[1] == 'X') {
			val = strtoul(str + 2, &eptr, 16);
			if(eptr == str + 2) return NULL;
		}
		else {
			val = strtoul(str + 1, &eptr, 8);
			if(eptr == str + 1) return NULL;
		}
	}
	else {
		val = strtoul(str, &eptr, 10);
		if(str == eptr) return NULL;
	}

	token->m_tokentype = TOKEN_NUMBER;
	token->m_begin = str;
	token->m_end = eptr;

	token->m_parsed = malloc(sizeof(long));
	memcpy(token->m_parsed, &val, sizeof(long));
	return eptr;
}


// string parsing. meant to be as simple as possible.
const char* GetString(const char* str, Token* token) {
	if(str[0] != '\"') return NULL;
	//starting double quote detected

	//get length & string limit
	const char* eptr = str + 1;
	size_t slen = 0;
	while(*eptr) {
		if(*eptr == '\"') break; //ending double quote
		else if(*eptr == '\\') { //escape character
			eptr++;
			slen++;

			switch(*eptr) {
			case 'n':
			case 'r':
			case 't':
			case '\'':
			case '\"':
				break;

			case 'x':
				if(isxdigit(*(eptr + 1)) && isxdigit(*(eptr + 2))) {
					break;
				}
				eptr += 2;
				return NULL;

			default:
				return NULL;
			}
		}
		else slen++;
		eptr++;
	}
	if(*eptr != '\"') return NULL;
	eptr++;

	token->m_tokentype = TOKEN_STRING;
	token->m_begin = str;
	token->m_end = eptr;
	token->m_parsed = malloc(slen);

	//read string
	char* out = (char*)token->m_parsed;
	str++;
	while(str) {
		if(*str == '\"') break;
		else if(*str == '\\') {
			int ch;
			str++;

			switch(*str) {
			case 'n':   *out = '\n'; break;
			case 'r':   *out = '\r'; break;
			case 't':   *out = '\t'; break;
			case '\'':  *out = '\''; break;
			case '\"':  *out = '\"'; break;
			case 'x':
				str++;
				sscanf(str + 1, "%02X", &ch);
				str++;
				*out = ch;
				break;
			}
		}
		else *out = *str;
		str++;
		out++;
	}
	
	//end.
	return token->m_end;
}



const char* GetOperator(const char* str, Token* token) {
	static const char* opstr[] = {
		"+=", "-=", "*=", "/=", "%=",
		"++", "--", "+", "-", "*", "/", "%", "and", "or", "xor",
		"==", "!=", "<=", ">=", "<", ">", "="
	};
	int opn = sizeof(opstr) / sizeof(const char*);
	int i;
	int slen;

	for(i = 0 ; i < opn ; i++) {
		if(_strnicmp(str, opstr[i], strlen(opstr[i])) == 0) break;
	}
	if(i == opn) return NULL;

	slen = strlen(opstr[i]);
	token->m_tokentype = TOKEN_OPERATOR;
	token->m_begin = str;
	token->m_end = str + slen;
	token->m_parsed = malloc(slen);
	memcpy(token->m_parsed, opstr[i], slen);
	return str + slen;
}


const char* GetLParen(const char* str, Token* token) {
	if(str[0] == '(') {
		token->m_tokentype = TOKEN_LPAREN;
		token->m_begin = str;
		token->m_end = str + 1;
		token->m_parsed = malloc(1);
		memcpy(token->m_parsed, str, 1);
		return str + 1;
	}
	else return NULL;
}


const char* GetRParen(const char* str, Token* token) {
	if(str[0] == ')') {
		token->m_tokentype = TOKEN_RPAREN;
		token->m_begin = str;
		token->m_end = str + 1;
		token->m_parsed = malloc(1);
		memcpy(token->m_parsed, str, 1);
		return str + 1;
	}
	else return NULL;
}

const char* GetSeperator(const char* str, Token* token) {
	if(str[0] == ',') {
		token->m_tokentype = TOKEN_SEPERATOR;
		token->m_begin = str;
		token->m_end = str + 1;
		token->m_parsed = malloc(1);
		memcpy(token->m_parsed, str, 1);
		return str + 1;
	}
	else return NULL;
}


const char* SkipSpace(const char* str);

const char* GetToken(const char* str, Token* token) {
	const char* ret;
	int tokenizern = sizeof(TokenParser) / sizeof(const char*(*)(const char*, Token*));
	int i;

	str = SkipSpace(str);
	for(i = 0 ; i < tokenizern ; i++) {
		ret = TokenParser[i](str, token);
		if(ret != NULL) break;
	}

	if(ret == NULL) return NULL;
	return ret;
}



const char* SkipSpace(const char* str) {
	while(strchr(" \t", *str) != NULL) str++;
	if(*str == ';') { //comment
		while(*str) str++;
	}

	return str;
}



Token* TokenizeLine(const char* line, int *tokenn) {
	Token toklist[512];
	int tokidx = 0;

	while(tokidx < 1024 && (line = GetToken(line, &toklist[tokidx++])) && *line);
	if(line && *line == '\0') {
		Token* tok = (Token*)malloc(sizeof(Token) * tokidx);
		memcpy(tok, toklist, sizeof(Token) * tokidx);
		*tokenn = tokidx;
		return tok;
	}

	else {
		tokidx--;
		while(--tokidx >= 0) {
			free(toklist[tokidx].m_parsed);
		}
		return NULL;
	}
}

